#include "spider.h"

#include "session.h"

#include "define.h"
#include "log.h"
#include "util.h"

#include <string.h>
#include <random>

Spider &Spider::GetInstance()
{
    static Spider instance;
    return instance;
}

static void _OnSpiderCheck(uv_timer_t *handle)
{
    Spider *pSpider = (Spider *)uv_handle_get_data((uv_handle_t *)handle);
    if (pSpider == NULL)
    {
        uv_timer_stop(handle);
        return;
    }
    pSpider->OnSpiderCheck();
}

static void _Run_Loop(void *arg)
{
    Spider *pSpider = (Spider *)arg;

    pSpider->RunLoop();
    return;
}

static void _OnAllocBuff(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)
{
    auto pSpider = (Spider *)uv_handle_get_data(handle);

    buf->base = pSpider->m_recvBuf + pSpider->m_recvLen;
    buf->len = TCP_BUFF_SIZE - pSpider->m_recvLen;
    return;
}

static void _OnReadData(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
{
    LOG_DEBUG("_OnReadData");
    auto pSpider = (Spider *)uv_handle_get_data((uv_handle_t *)stream);
    if (pSpider == nullptr)
    {
        return;
    }
    if (nread > 0)
    {
        pSpider->HandReaddata(nread);
    }
    else if (nread < 0)
    {
        pSpider->Close();
    }

    return;
}

static void _OnWriteData(uv_write_t *req, int status)
{
    LOG_DEBUG("_OnWriteData");
    auto pSpider = (Spider *)uv_handle_get_data((uv_handle_t *)req);
    if (pSpider == nullptr)
    {
        free(req);
        return;
    }
    if (status != 0)
    {
        LOG_ERROR("Spider [%s] Send Message Failed", g_Uuid.c_str());
        pSpider->Close();
    }
    free(req);
    return;
}

void _OnResolved(uv_getaddrinfo_t *req, int status, struct addrinfo *res)
{
    LOG_DEBUG("_OnResolved");
    auto pSpider = (Spider *)uv_req_get_data((uv_req_t *)req);
    if (status == 0)
    {
        char ip[INET6_ADDRSTRLEN];
        uv_ip4_name((struct sockaddr_in*)res->ai_addr, ip, sizeof(ip));
        LOG_INFO("Connect Eagle [%s:%u]", ip, htons(((struct sockaddr_in*)res->ai_addr)->sin_port));
        pSpider->DoConnectEagle(res->ai_addr);
        uv_freeaddrinfo(res);
    }
    else
    {
        LOG_ERROR("getaddrinfo error: %s", uv_strerror(status));
        pSpider->Close();
    }
}

static void _OnConnectEagle(uv_connect_t *req, int status)
{
    LOG_DEBUG("_OnConnectEagle %d", status);
    auto pSpider = (Spider *)uv_req_get_data((uv_req_t *)req);
    if (status == 0)
    {
        pSpider->DoRegister();
        free(req);
        return;
    }
    else
    {
        pSpider->Close();
    }
    free(req);
}

bool Spider::Start()
{
    if (g_Uuid.length() > UDID_SIZE - 1)
    {
        LOG_DEBUG("UUid: %s is too long", g_Uuid.c_str());
        return false;
    }
    srand(static_cast<unsigned int>(time(0)));    
    m_loop = uv_loop_new();
    if (!m_loop)
    {
        LOG_DEBUG("Loop create failed");
        exit(1);
    }
    m_commandTimeout = g_CommandTimeout;
    m_idleTimeout = g_IdleTimeout;
    m_lastRecvTick = uv_now(m_loop);
    uv_timer_init(m_loop, &m_spiderTimer);    
    uv_handle_set_data((uv_handle_t *)&m_spiderTimer, this);

    uv_timer_start(&m_spiderTimer, _OnSpiderCheck, 0, g_timerRepeat);
    if (uv_thread_create(&m_tid, _Run_Loop, this) != 0)
    {
        LOG_ERROR("master create thread failed");
        return false;
    }
    m_status = SPIDER_STATUS_INIT;
    return true;
}

void Spider::Stop()
{
}

void Spider::OnSpiderCheck()
{
    uint64_t nowTime = uv_now(m_loop);
    switch (m_status)
    {
    case SPIDER_STATUS_INIT:
        if (m_lastTick + g_HeartbeatInterval < nowTime)
        {
            if (!DoGetEagleAddr())
            {
                LOG_ERROR("Do register error");
            }
            m_lastTick = nowTime;
        }
        break;
    case SPIDER_STATUS_RUN:
        if (m_lastTick + g_HeartbeatInterval < nowTime)
        {
            if (!DoSendHeartbeat())
            {
                LOG_ERROR("Do heartbeat error");
            }
            m_lastTick = nowTime;
        }
        if (m_lastRecvTick + g_HeartbeatInterval * 3 < nowTime)
        {
            LOG_ERROR("Do heartbeat timeout");
            m_lastRecvTick = nowTime;
            Close();
        }
        break;
    }
}

void Spider::RunLoop()
{
    uv_run(m_loop, UV_RUN_DEFAULT);
}

void Spider::HandReaddata(ssize_t nread)
{
    LOG_DEBUG("Spider::HandReaddata: %d", nread);
    m_lastRecvTick = uv_now(m_loop);
    m_recvLen += nread;
    char *recvPos = m_recvBuf;
    do
    {
        if (m_recvLen < sizeof(MessageHeader))
        {
            if (recvPos != m_recvBuf)
            {
                memmove(m_recvBuf, recvPos, m_recvLen);
                recvPos = m_recvBuf;
            }
            return;
        }
        MessageHeader *header = (MessageHeader *)recvPos;
        LOG_DEBUG("Spider::HandRead MessageHeader type: %d", header->type);
        switch (header->type)
        {
        case MESSAGE_REGISTER_RESP:
            if (m_recvLen < sizeof(MessageRegisterResp))
            {
                if (recvPos != m_recvBuf)
                {
                    memmove(m_recvBuf, recvPos, m_recvLen);
                    recvPos = m_recvBuf;
                }
                return;
            }
            if (!DoRegisterResp((MessageRegisterResp *)recvPos))
            {
                Close();
                return;
            }
            recvPos += sizeof(MessageRegisterResp);
            m_recvLen -= sizeof(MessageRegisterResp);
            break;
        case MESSAGE_HEARTBEAT_RESP:
            DoHeartbeatResp((MessageHeartbeat *)recvPos);
            recvPos += sizeof(MessageHeartbeat);
            m_recvLen -= sizeof(MessageHeartbeat);
            break;
        case MESSAGE_TCP_REQ:
            if (m_recvLen < sizeof(MessageTcp))
            {
                if (recvPos != m_recvBuf)
                {
                    memmove(m_recvBuf, recvPos, m_recvLen);
                    recvPos = m_recvBuf;
                }
                return;
            }
             if (m_recvLen < sizeof(MessageTcp) + ((MessageTcp *)recvPos)->targetLen)
            {
                if (recvPos != m_recvBuf)
                {
                    memmove(m_recvBuf, recvPos, m_recvLen);
                    recvPos = m_recvBuf;
                }
                return;
            }
            LOG_DEBUG("MessageTcp: %d, targetLen :%u", sizeof(MessageTcp), ((MessageTcp *)recvPos)->targetLen);
            if (!DoTcpReq((MessageTcp *)recvPos)) {
                Close();
                return;
            }
            m_recvLen -= sizeof(MessageTcp);
            m_recvLen -= ((MessageTcp *)recvPos)->targetLen;
            recvPos += sizeof(MessageTcp);
            recvPos += ((MessageTcp *)recvPos)->targetLen;            
            break;
        case MESSAGE_UDP_REQ:
            if (m_recvLen < sizeof(MessageUdp))
            {
                if (recvPos != m_recvBuf)
                {
                    memmove(m_recvBuf, recvPos, m_recvLen);
                    recvPos = m_recvBuf;
                }
                return;
            }
            if (!DoUdpReq((MessageUdp *)recvPos)) {
                Close();
                return;
            } 
            m_recvLen -= sizeof(MessageUdp);
            recvPos += sizeof(MessageUdp);            
            break;
        default:
            LOG_INFO("Unsupported MsgType %d", header->type);
            Close();
            return;
            break;
        }
    } while (true);
}

bool Spider::DoGetEagleAddr()
{
    LOG_DEBUG("DoGetEagleAddr");
    struct addrinfo hints;
    hints.ai_family = PF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = 0;
    uv_req_set_data((uv_req_t *)&m_resolver, this);
    int r = uv_getaddrinfo(m_loop, &m_resolver, _OnResolved, g_Addr.c_str(), g_Port.c_str(), &hints);
    if (r)
    {
        LOG_ERROR("getaddrinfo call error %s", uv_err_name(r));
        return false;
    }
    return true;
}

void Spider::DoConnectEagle(struct sockaddr *target)
{
    LOG_DEBUG("DoConnectEagle");
    uv_tcp_init(m_loop, &m_eagleTcp);
    if (g_BindAddr.size() > 0) {
        // 设置本地地址绑定
        struct sockaddr_in bind_addr;
        uv_ip4_addr(g_BindAddr.c_str(), 0, &bind_addr); // 使用你想要绑定的本地IP地址，端口号设为0以让系统自动分配
        uv_tcp_bind(&m_eagleTcp, (const struct sockaddr*) &bind_addr, 0);
    }
    uv_handle_set_data((uv_handle_t *)&m_eagleTcp, this);
    uv_connect_t *eagleConn = (uv_connect_t *)calloc(1, sizeof(uv_connect_t));
    if (eagleConn) {
        uv_req_set_data((uv_req_t *)eagleConn, this);

        memcpy(&m_eagleAddr, target, sizeof(struct sockaddr *));
        int ret = uv_tcp_connect(eagleConn, &m_eagleTcp, target, _OnConnectEagle);
        if (ret < 0) {
            LOG_ERROR("DoConnectEagle tcp connect failed %d", ret);
            free(eagleConn);
            Close();
        }
    } else {
        LOG_ERROR("DoConnectEagle calloc connect failed");
    }
}

void Spider::DoRegister()
{
    LOG_DEBUG("DoRegister");
    uv_read_start((uv_stream_t *)&m_eagleTcp, _OnAllocBuff, _OnReadData);
    std::string udid = g_Uuid;
    std::string nonce;

    const char *characterCode = "qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM";
    std::random_device rd;
    std::uniform_int_distribution<int> dist(0, (int)(strlen(characterCode) - 1));
    for (int i = 0; i < NONCE_SIZE - 1; i++)
    {
        nonce.push_back(characterCode[dist(rd)]);
    }
    std::string cipher = md5(udid + nonce + g_Cipher);
    auto msg = (MessageRegister *)m_sendBuf + m_sendLen;
    msg->header.type = MESSAGE_REGISTER_REQ;
    memcpy(msg->udid, udid.c_str(), udid.length());
    memcpy(msg->nonce, nonce.c_str(), nonce.length());
    memcpy(msg->cipher, cipher.c_str(), cipher.length());
    m_sendLen += sizeof(MessageRegister);

    LOG_DEBUG("DoRegister udid %s", udid.c_str());
    LOG_DEBUG("DoRegister nonce %s", nonce.c_str());
    LOG_DEBUG("DoRegister cipher %s", cipher.c_str());
    DoSendToEagle();
}

bool Spider::DoRegisterResp(MessageRegisterResp *resp)
{
    if (resp->code == 0)
    {
        m_status = SPIDER_STATUS_RUN;
        if (resp->commandTimeout != 0) {
            m_commandTimeout = resp->commandTimeout;
        }
        if (resp->idleTimeout != 0) {
            m_idleTimeout = resp->idleTimeout;
        }
        LOG_INFO("uuid [%s] Online", g_Uuid.c_str());
    }
    else
    {
        LOG_DEBUG("DoRegister Resp %d", resp->code);
        return false;
    }
    return true;
}

void Spider::DoHeartbeatResp(MessageHeartbeat *resp)
{
    LOG_DEBUG("DoHeartbeat Resp %lu delay", uv_now(m_loop) - resp->timestamp);
}

bool Spider::DoTcpReq(MessageTcp *req)
{
    auto pSession = new Session();
    if (pSession == nullptr)
    {
        return false;
    }
    if (!pSession->Init(this, SESSION_MODEL_TCP, (uint8_t *)req))
    {
        LOG_ERROR("Session: %08x tcp Init failed", pSession->m_sessionId);
        delete pSession;
        return false;
    } 
    return true;
}

bool Spider::DoUdpReq(MessageUdp *req)
{
    auto pSession = new Session();
    if (pSession == nullptr)
    {
        return false;
    }
    if (!pSession->Init(this, SESSION_MODEL_UDP, (uint8_t *)req))
    {
        LOG_ERROR("Session: %08x udp Init failed", pSession->m_sessionId);
        delete pSession;
        return false;
    }
    return true;
}


void Spider::Close()
{
    LOG_DEBUG("Close");
    if (uv_is_active((uv_handle_t *)&m_eagleTcp)) {
        uv_read_stop((uv_stream_t *)&m_eagleTcp);
    }    
    if (!uv_is_closing((uv_handle_t *)&m_eagleTcp)) {
        uv_close((uv_handle_t *)&m_eagleTcp, NULL);
        LOG_DEBUG("Close m_eagleTcp");
    }
    m_sendLen = 0;
    m_recvLen = 0;

    m_status = SPIDER_STATUS_INIT;
}

bool Spider::DoSendHeartbeat()
{
    auto msg = (MessageHeartbeat *)m_sendBuf + m_sendLen;
    msg->header.type = MESSAGE_HEARTBEAT_REQ;
    msg->timestamp = uv_now(m_loop);
    m_sendLen += sizeof(MessageHeartbeat);

    LOG_DEBUG("DoSendHeartbeat udid %s", g_Uuid.c_str());
    DoSendToEagle();
    return true;
}

void Spider::DoSendToEagle()
{
    auto writeReq = (uv_write_t *)calloc(1, sizeof(uv_write_t));
    if (writeReq == nullptr)
    {
        LOG_ERROR("Spider: calloc write Req Failed");
        Close();
        return;
    }
    uv_handle_set_data((uv_handle_t *)writeReq, (void *)this);
    uv_buf_t buf = uv_buf_init((char *)m_sendBuf, m_sendLen);
    if (uv_write(writeReq, (uv_stream_t *)&m_eagleTcp, &buf, 1, _OnWriteData) < 0)
    {
        LOG_ERROR("Spider: write Req Failed");
        free(writeReq);
        Close();
    }
    m_sendLen = 0;
}